package main;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Stack;


public class ScoreImage extends BufferedImage {

	private Score score;
	private int cursor = 0;
	private double currentBeat = 0;
	private Graphics2D g;
	private ArrayList<Staff> barLines = new ArrayList<Staff>();
	private int currentStaffIndex = 0;
	private Staff currentStaff;
	private int CURSOR_RESET;
	private BufferedImage notesImage;
	private Graphics2D notesGraphics;
	
	/**
	 * Creates the buffered image of the score
	 * @param w width
	 * @param h height
	 * @param s score
	 */
	public ScoreImage(int w, int h, Score s) {
		super(w, h, BufferedImage.TYPE_INT_ARGB);	
		score = s;
		g = createGraphics();
		createBarLines();
		paint();
		notesImage = new BufferedImage(w,h, BufferedImage.TYPE_INT_ARGB);
		notesGraphics = notesImage.createGraphics();
		setUpNotesImage();
		currentStaff = barLines.get(currentStaffIndex);
		notesGraphics.transform(currentStaff.getTransform());
	}


	/**
	 * Sets up the over laid image that handles the notes
	 */
	private void setUpNotesImage() {
		notesGraphics.setBackground(new Color(255,255,255,0));
		notesGraphics.clearRect(0, 0, notesImage.getWidth(), notesImage.getHeight());
		notesGraphics.setColor(Color.BLACK);
		notesGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
	}

	/**
	 * Generates the barlines
	 */
	public void createBarLines(){
		AffineTransform transform = AffineTransform.getTranslateInstance(Score.STAFF_OFFSET_X,Score.STAFF_OFFSET_Y);
		int lines = score.getStaves();
		for(int i = 0; i<lines; i++){
			double yOffset = (i+1)*transform.getTranslateY()*2 + i*Score.TOTAL_STAFF_SIZE;
			barLines.add(new Staff((int)transform.getTranslateX(), (int)yOffset));
		}
	}

	/**
	 * Paints the image
	 */
	public void paint(){
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setBackground(Color.WHITE);
		g.clearRect(0, 0, getWidth(), getHeight());
		g.setColor(Color.BLACK);

		KeySignature keySig = score.getKeySignature();
		TimeSignature timeSig = score.getTimeSignature();
		Clef clef = score.getClef();

		for(int i = 0; i < barLines.size(); i++){
			Staff s = barLines.get(i);
			AffineTransform t =s.getTransform();
			g.transform(t);
			s.draw(g);
			cursor = clef.draw(g, cursor);
			cursor = keySig.draw(g, cursor);
			cursor = timeSig.draw(g, cursor, score);
			try{g.transform(t.createInverse());} catch(NoninvertibleTransformException e){}
			CURSOR_RESET = cursor;
		}
	}

	/**
	 * Draws a measure line
	 */
	private void drawMeasureLine() {
		Rectangle2D.Double measureLine = new Rectangle2D.Double(cursor, 0, Score.MEASURE_LINE_WIDTH, Score.TOTAL_STAFF_SIZE);
		notesGraphics.fill(measureLine);
		cursor += Note.SPACE_BETWEEN_NOTES;
	}

	/**
	 * paints individual notes given a list
	 * @param notes list of notes to be painted
	 */
	public void paintNotes(ArrayList<Note> notes){
		for (int i = 0; i<notes.size(); i++){
			if(currentBeat == Score.FRACTIONAL_BPMEASURE){
				drawMeasureLine();
				currentBeat = 0;
			}
			Note n = notes.get(i);
			cursor = n.draw(notesGraphics, cursor);
			currentBeat += n.getDuration();
			if (cursor > Score.BAR_LINE_LENGTH-Score.NOTE_HEAD_SIZE){
				wrap();
			}
		}
		g.drawImage(notesImage, 0, 0, null);
	}

	/**
	 * Wraps to the next line
	 */
	private void wrap() {
		AffineTransform curr = notesGraphics.getTransform();
		try{notesGraphics.transform(curr.createInverse());} catch(NoninvertibleTransformException e){}
		currentStaffIndex++;
		currentStaff = barLines.get(currentStaffIndex);
		notesGraphics.transform(currentStaff.getTransform());
		cursor = CURSOR_RESET;
	}

}
